﻿using IronLua.Metadata;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace IronLua.Runtime
{
    internal sealed class Stack
        : IReadOnlyList<Value>
    {

        private readonly Thread thread;
        private readonly List<Value> stack;

        public Stack(Thread thread)
        {
            this.thread = thread;
            this.stack = new List<Value>();
        }

        public Value    this[int index] { get { return this.stack[index]; } set { this.SetStackValue(index, ref value); } }

        public int      Count { get { return this.stack.Count; } }

        public int Push(params Value[] values)
        {
            stack.AddRange(values);
            return this.Count - 1;
        }

        public bool TryPop(out Value value)
        {
            if (this.Count == 0)
            {
                value = Value.Nil;
                return false;
            }

            var removeIndex = this.Count - 1;
            value = this.stack[removeIndex];
            this.stack.RemoveAt(removeIndex);
            return true;            
        }

        public Value TryPopParameterOrDefault(Value valueIfMissing = default(Value))
        {
            var frame = this.thread.Frames.LastOrDefault();
            var index = frame.BaseStackIndex;

            if (index < this.Count)
            {
                var value = this.stack[index];
                this.stack.RemoveAt(index);
                return value;
            }

            return valueIfMissing;
        }

        public bool TryPopAt(int index, out Value value)
        {
            if (index < this.Count)
            {
                value = this.stack[index];
                this.stack.RemoveAt(index);
                return true;
            }

            value = default(Value);
            return false;
        }

        public bool TryPopUpTo(int index, out IEnumerable<Value> results)
        {
            if (index < 0 || index > this.Count - 1)
            {
                results = null;
                return false;
            }

            var elements = new List<Value>(this.Count - index);

            while (this.Count > index)
            {
                var popped = Value.Nil;
                if (!this.TryPop(out popped))
                {
                    results = null;
                    return false;
                }
                elements.Add(popped);
            }

            results = elements;
            return true;
        }

        public void TryPopFrom(int index)
        {
            while (index < this.Count)
            {
                this.stack.RemoveAt(this.Count - 1);
            }
        }

        public void TryPopFromTo(int startIndex, int endIndex)
        {
            var delta = endIndex - startIndex + 1;

            if (delta <= 0)
            {
                return;
            }

            if (delta == 1)
            {
                this.stack.RemoveAt(startIndex);
                return;
            }

            while (delta-- > 0)
            {
                this.stack.RemoveAt(startIndex);
            }
        }

        public Value ParameterOrDefault(int index, Value valueIfMissing)
        {
            var frame = this.thread.Frames.LastOrDefault();
            return this.ElementOrDefault(frame.BaseStackIndex + index, valueIfMissing);
        }

        public Value ElementOrDefault(int index, Value valueIfMissing)
        {
            if (index < this.Count)
            {
                return this.stack[index];
            }

            return valueIfMissing;
        }

        public IEnumerator<Value> GetEnumerator()
        {
            return this.stack.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        public override string ToString()
        {
            return "Thread Stack ({0} values)".FormatWith(this.Count);
        }

        private void SetStackValue(int index, ref Value value)
        {
            if (index < 0)
            {
                throw new ArgumentOutOfRangeException("index");
            }

            if (index < this.Count)
            {
                this.stack[index] = value;
                return;
            }

            var delta = (index - this.Count) + 1;
            this.Push(new Value[delta]);
            this.SetStackValue(index, ref value);
        }

    }
}
